#!/usr/bin/env python
#
# Copyright 2008 Richard J. Bell. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

""" Wiki classes for geo wiki site. """

# Python Imports
import re
import urllib
import urlparse
import time
import datetime
import os
from django.utils import simplejson as json
import sys
import logging

# Google App Engine Imports
from google.appengine.api import memcache
from google.appengine.ext import db
from google.appengine.ext.db import polymodel
from google.appengine.ext import webapp

# Application Imports
from app.gwconfig import GW_TYPE_ICON
from app.gwconfig import GW_TYPE_POLYLINE
from app.gwconfig import GW_TYPE_POLYGON
from app.gwconfig import GW_TYPE_KML
from app.gwconfig import GW_POLY_WIDTH
from app.gwconfig import GW_POLY_COLOR
from app.gwconfig import GW_POLY_OPACITY
from app.gwconfig import GW_POLY_FILLOPACITY
from app.gwconfig import GW_SCALE
from app.gwbaserequesthandler import GWRequestHandler
from app.gwuser import GWUser
from app.gwadminhandlers import Color
from app.gwadminhandlers import Marker
from app.gwwikify import wikify
from app.gwxml import Xml2Obj

#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# Geo wiki page
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
class GWPage( db.Model ):
    """ A wiki page """
    wikiTitle = db.StringProperty( required = True, multiline = False )         # unique wikiword, no spaces camel case
    revision = db.IntegerProperty( required = True, default = 0 )               # the revision, as pages are revised this is incremented
    current = db.BooleanProperty( default = False )                             # if true this page is current, there may be other older pages present
    locked = db.BooleanProperty( default = False )                              # if true this page is locked and can not be edited or deleted
    title = db.StringProperty( multiline = False, default = '' )                # conventional title
    content = db.TextProperty( default = '')                                    # HTML page content
    author = db.ReferenceProperty( GWUser, required = True )                    # most recent author of this page
    date = db.DateTimeProperty( auto_now = True )                               # most recent revision date for this page
    scale = db.IntegerProperty( required = True, default = 0 )                  # scale at which this feature should be displayed.  If the diagonal map distance is >= scale display the feature
    tags = db.StringListProperty( default = [] )                                # list of tags associated with this wiki page
    featurepages = db.StringListProperty( default = [] )                        # list of pages who's features should be displayed on this page
    featuretags = db.StringListProperty( default = [] )                         # list of tags for pages who's features should be displayed on this page, fixme: add support for this
    bboxEast = db.FloatProperty()                                               # the page's bounding box inclusive of all of the page's geo features
    bboxWest = db.FloatProperty()
    bboxSouth = db.FloatProperty()
    bboxNorth = db.FloatProperty()
    viewObj = db.TextProperty( )                                                # holds json response object for page, its features, and its featurepages
                                                                                # Used to support page view since this is the more common need.  Page edit
                                                                                # is supported by dynamically building the reqired json object.
                                                                                # fixme: the view object can be invalidated whenever a page is added/edited via changes to featurepages
                                                                                # fixme: or featuretags.  Tags can be changed no another page.  Likewise the other page's underlying feature
                                                                                # fixme: may be changed.
    
    def json(self, xWikify = False):                                            # create a json.dumps compatable version of this page
        """ Create a json.dumps compatable object for this page 
            exclusive of it's features. 
        """
        if xWikify:
            content = wikify( self.content )
        else:
            content = self.content
        obj = {
            'wikiTitle': self.wikiTitle,
            'revision': self.revision,
            'current': self.current,
            'locked': self.locked,
            'title': self.title,
            'content': content,
            'author': str( self.author.key() ),
            'nickname': self.author.user.nickname(),
            'date': self.date.strftime('%Y-%m-%d %H:%M'),
            'scale': self.scale,
            'tags': self.tags,
            'featurepages': self.featurepages,
            'featuretags': self.featuretags,
            'bboxEast': self.bboxEast,
            'bboxWest': self.bboxWest,
            'bboxNorth': self.bboxNorth,
            'bboxSouth': self.bboxSouth
            }
        return obj
    def jsonf( self, xWikify = False):                                          # create a json.dumps compatable version of this page and its features (suitable for edit)
        """ Create a json.dumps compatable ofject for this page 
            and it's features. Suitable for editing.
        """
        obj = self.json()                                                       # make a json.dumps compatible dictionary
        features = GWFeature.list( self.wikiTitle )                             # get the page's features
        obj[ 'features' ] = [ f.json() for f in features]                       # put them in a json.dumps compatiable array of dictionaries
                                                # fixme: need to check that features go into list only once, track feature key
        return obj
    def jsonff( self ):                                                         # create a json.dumps compatable version of this page, its features, 
                                                                                # and its feature pages (suitable for view)
        """ Create a json.dumps compatable ofject for this page 
            and it's features. Suitable for editing.
        """
        obj = self.json()                                                       # make a json.dumps compatible dictionary
        features = GWFeature.list( self.wikiTitle )                             # get the page's features
        #logging.info('==> adding features for page %s' % self.wikiTitle )
        #logging.info('==> adding features for pages %s' % self.featurepages )
        for p in self.featurepages:                                             # fixme: need to check that page is in list only once
            fp = GWFeature.list( p )
            features.extend( fp )
        if len(self.featuretags) > 0:
            xpages = GWPage.all()
            xpages = xpages.filter( 'tags IN', self.featuretags )               # fixme: need to check that tag is in list only once
            #logging.info('==> adding features for tags %s' % self.featuretags )
            for p in xpages:
                #logging.info('==> adding features for tage page %s' % p.wikiTitle )
                fp = GWFeature.list( p.wikiTitle )                              # fixme: need to check that features go into list only once, track feature key
                features.extend( fp )
        obj[ 'features' ] = [ f.json() for f in features]                       # put them in a json.dumps compatiable array of dictionaries
        return obj
    @staticmethod
    def eraseAll():                                                             # erase all pages
        q = GWPage.all()
        for r in q: 
            db.delete( r )
        logging.info('-----> erased all pages');
        GWFeature.eraseAll()
        return 'Erased all pages and features.'
    @staticmethod
    def load( replaceExitingPages ):                                            # load predefined pages
        report = ''
        files = os.listdir( 'pages' )
        for f in files:
            # if not XML or HTML, ignour
            fName, fExt = os.path.splitext( f )
            logging.info('-----> load page %s' % f)
            if fExt == '.xml':
                report += GWPage.loadXML( f, replaceExitingPages )
            elif fExt == '.html':
                report += GWPage.loadHTML( f, fName, replaceExitingPages )
        return report
    @staticmethod
    def loadTitle( content, fname ):                                            # get and format the title, use first <h1>
        title = re.search( '<H1.*?>(.*?)</H1>', content, re.I)
        if title: 
            title = title.group(1)
            wikiTitle = title
            # legal wikiTitle characters are A-Z, a-z, 0-9, -_.  Turn all others into _
            (wikiTitle, n) = re.subn( "[^a-zA-Z_\-]", '_', title )
            return (wikiTitle, title, '')
        else:
            return ( None, None, 'ERROR: Page %s needs an H1 title.<br />' % fname )
    @staticmethod
    def loadCheck( wikiTitle, replaceExistingPages ):                           # check if loading the page is permissable and create a default page
        report = ''
        page = GWPage.gql('WHERE wikiTitle = :1 AND current = TRUE', wikiTitle).get()
        if not page:                                                            # no page, just make one
            page = GWPage.newPage( wikiTitle=wikiTitle )
            return (page, '%s created<br />' % wikiTitle)
        if replaceExistingPages == 'false':
            return (None, '<b>***** Page %s exists.  It was NOT loaded</b>.<br />' % wikiTitle )
        # page exists but we should replace it
        page.current = False
        revision = page.revision + 1
        page.put()
        page = GWPage.newPage( wikiTitle=wikiTitle, revision=revision )
        return (page, 'GW page %s exists.  It was replaced.<br />' % wikiTitle )
    @staticmethod
    def newPage( wikiTitle,                                                     # create a new default page object # fixme: check if used and good
                 title='', 
                 revision=1,
                 current=True,
                 locked=False,
                 content='',
                 scale=999999,
                 tags=[],
                 featurespages=[],
                 featuretags=[],
                 viewobj=''):
        """ Initialize a new page with defaults. """
        page = GWPage(wikiTitle=wikiTitle, 
                      revision=revision, 
                      current=current,
                      locked=locked, 
                      title=title, 
                      content=content, 
                      author=GWUser.get(), 
                      scale=scale,
                      tags=tags,
                      featurespages=featurespages,
                      featuretags=featuretags,
                      viewobj=viewobj)
        return page
    @staticmethod
    def loadXML( f, replaceExitingPages ):                                      # create a page from XML
        """ Create a page object from an XML object """
        xml = Xml2Obj()
        report = ''
        root = xml.Parse( 'pages/' + f )
        content = root.getElementData( 'content' )                              # get content
        ( wikiTitle, title, report) = GWPage.loadTitle( content, f )            # get the wikiTitle and title from the content
        if( not wikiTitle ):                                                    # no wikiTitle, return error message
            return report
        (page, report) = GWPage.loadCheck( wikiTitle, replaceExitingPages )     # get page or die
        if not page:
            return report
        page.wikiTitle = wikiTitle.lower()                                      # set wikiTitle
        locked = root.attributes.get( 'locked', "False" )                       # get locked
        page.locked = (locked == 'True' )
        page.scale = int(root.attributes.get( 'scale', GW_SCALE ))              # set scale
        page.title = title                                                      # set title
        page.content = content                                                  # set content
        page.tags = [ tx.attributes['tag'].lower() for tx in root.getElements( 'tag' )] # get tags
        page.featurepages = [ fx.attributes['page'].lower() for fx in root.getElements( 'featurepage' )] # get features
        page.featuretags = [ tx.attributes['tag'].lower() for tx in root.getElements( 'featuretag' )] # get features
        # fixme: this should support comments embeded in pages report += GWComment.xmlInit(root, page)
        report += GWFeature.xmlInit(root, page)                                 # process page's features
        page.setBBox()                                                          # set the bounding box 
        page.viewObj = None                                                     # defer viewObj value untill it's needed
        page.put()                                                              # save page
        return report
    @staticmethod
    def loadHTML( fpath, fname, replaceExitingPages ):                          # create a page from HTML
        """ Create a page object from an html file 
            The file name is used as the wikiTitle.
            The title is set by the <h1>...</h1> text.
            META tags are used to set the folowing:
                locked - <META NAME="locked" CONTENT="true">, otherwise false
                scale - <META NAME="scale" CONTENT="9">, otherwise GW_SCALE
                tags - <META NAME="tag" CONTENT="tag">
                featurepages - <META NAME="featurepages" CONTENT="pageWikiTitle">
                featuretags - <META NAME="featuretag" CONTENT="pageTag">
        """
        inFile = open( 'pages/' + fpath )                                       # get the document
        html = inFile.read()
        inFile.close()
        content = re.search( '(<BODY .*?>)(.*)(</BODY>)', html, re.I+re.M+re.S) # get the content, <BODY ... </BODY>
        content = content.group(2)
        ( wikiTitle, title, report) = GWPage.loadTitle( content, fpath )        # get the wikiTitle and title from the content
        if( not wikiTitle ):                                                    # no wikiTitle, return error message
            return report
        wikiTitle = wikiTitle.lower()
        (page, report) = GWPage.loadCheck( wikiTitle, replaceExitingPages )     # get page or die
        if not page:
            return report
        page.wikiTitle = wikiTitle                                              # set wikiTitle
        locked = re.search( '<META NAME="locked" CONTENT="true">', html, re.I ) # get locked, <META NAME="locked" CONTENT="False">
        if locked: page.locked = True
        else: page.locked = False
        scale = re.search( '<META NAME="scale" CONTENT="(.*?)">', html, re.I )  # get scale, <META NAME="scale" CONTENT="11">
        if scale: page.scale = int(scale)
        else: page.scale = int( GW_SCALE )
        page.title = title                                                      # set title
        page.content = content                                                  # set content
        tags = re.findall( '<META NAME="tag" CONTENT="(.*?)">', html, re.I )    # get the tags, <META NAME="tag" CONTENT="help,tag2,tag3">
        page.tags = [t.lower() for t in tags]
        featurepages = re.findall( '<META NAME="featurepages" CONTENT="(.*?)">', html, re.I )    # get the features <META NAME="featurepages" CONTENT="feature1,feature2">
        page.featurepages = [fp.lower() for fp in featurepages]
        featuretags = re.findall( '<META NAME="featuretag" CONTENT="(.*?)">', html, re.I )       # get the features <META NAME="featuretag" CONTENT="feature1,feature2">
        page.featuretags = [ft.lower() for ft in featuretags]
        page.viewObj = None                                                     # defer viewObj value untill it's needed
        page.put()
        return report
    def setBBox( self ):                                                        # set the bounding box for this page
        features = GWFeature.all()
        features = features.filter( 'wikiTitle = ', self.wikiTitle )
        features = features.filter( 'revision = ', self.revision )
        lats = []
        lons = []
        for f in features:
            lats.extend( f.lats() )
            lons.extend( f.lons() )
        self.bboxEast = max( lons )
        self.bboxWest = min( lons )
        self.bboxSouth = min( lats )
        self.bboxNorth = max( lats )
        return
    def setJson( self ):                                                        # set the page's json value
        """ Create a json version of the page, its features, and its 
            feature pages.
        """
        obj = self.jsonff()
        viewObj = json.dumps( obj )
        # todo: modify this code to only erase the effected elements based on the page's tags
        GWQuery.eraseAll()
        return viewObj
    @staticmethod
    def query(                                                                  # return all pages matching this query
        pages=None,                                                             # wikiTitle list
        tags=None,                                                              # tag list
        current=True):                                                          #
        """ Query pages """
        # logging.info( '-----> page query' )
        xpages = GWPage.all()                                                   # build the query
        if pages: 
            pages = [ p.lower() for p in pages ]
            xpages = xpages.filter( 'wikiTitle IN', pages )
        if tags: 
            tags = [ t.lower() for t in tags]
            xpages = xpages.filter( 'tag IN', tags )
        if current: xpages = xpages.filter( 'current =', current )
        results = []
        for p in xpages:
            # logging.info( '       page %s' % p.wikiTitle )
            results.append( p )
        # logging.info( '       return %s items' % len(results) )
        return results

    @staticmethod
    def save( np ):                                                             # save a new page # fixme: check if used and good, fixme: is this still used
        """ Save a wiki page.
        If the page exists, the old one is made not current and a new revision 
        created.  fixme: need to revise for new structure and deal with features
        
        Parameters: page, dictionary: the page to save
        Returns: status, str: 'success' on success, 'error' on error
        """
        currentUser, wikiUser = GWUser.getUserInfo()
        revision = 1
        wikiTitle = np['wikiTitle']
        # Get the page.  If it exists get it's revision and mark it not current.
        page = GWPage.gql('WHERE wikiTitle = :1 AND current = TRUE', wikiTitle).get()
        if page:
            if page.locked:
                return 401, 'Resource locked'
            revision = page.revision + 1
            page.current = False;
            page.put()
        # save this page
        #bboxEast = db.FloatProperty()                                               # the page's bounding box inclusive of all of the page's geo features
        #bboxWest = db.FloatProperty()
        #bboxSouth = db.FloatProperty()
        #bboxNorth = db.FloatProperty()
        page = GWPage( 
            wikiTitle = wikiTitle, 
            revision = revision,
            current = True,
            locked = False,
            title = np['title'],
            content = np['content'],
            author = GWUser.get(),
            # date uses auto_now = true
            tags = np['tags'],
            features = np['features']
            # bb stuff here
            )
        page.put()
        return 200, 'Success'
    
class GWPageView(GWRequestHandler):                                             # Show a GeoWiki page
    """ Show a geo wiki page. """
    def get(self, wikiTitle):
        """ Show a geo wiki page. """
        #logging.info( '-----> GWPageView %s' % wikiTitle )
        wikiTitle = wikiTitle.lower()
        # get the page
        qPage = GWPage.all()
        qPage = qPage.filter( 'wikiTitle =', wikiTitle )
        qPage = qPage.filter( 'current =', True )
        page = qPage.get()
        if page:
            if page.viewObj == None:
                viewObj = page.setJson()  # fixme: verify that feature exists only once when building viewObj
                page.viewObj = viewObj
                page.put()
            self.generate( 'view.html', templateValues={ 'title': wikiTitle,
                                                         'page': page.viewObj })
        else:
            self.generate( 'admin/notfound.html', templateValues={ 'wikititle': wikiTitle } )
        return
class GWPageEdit(GWRequestHandler):                                             # Edit a GeoWiki page
    """ Show a geo wiki page. """
    def get(self, wikiTitle):
        """ Show a geo wiki page. """
        logging.info( '-----> GWPageEdit %s' % wikiTitle )
        # make sure user logged in
        if not GWUser.isLogin():
            self.generate('admin/loginrequired.html', templateValues={ 'title': 'Login Required' });
            return
        # get the page
        wikiTitle = wikiTitle.lower()
        qPage = GWPage.all()
        qPage = qPage.filter( 'wikiTitle =', wikiTitle )
        page = qPage.get()
        if not page:
            self.generate( 'admin/notfound.html', templateValues={ 'wikititle': wikiTitle } )
            return
        # get the page content
        # for editing we get ONLY the page's features, not any featurepages of 
        # featuretags features
        # if we ever do get the featurepages or featuretags features be sure to 
        # only get ONE copy of each feature
        obj = page.jsonf()
        obj = json.dumps( obj )
        markers = Marker.listAllNames()
        colors = Color.listAll()
        self.generate( 'edit.html', templateValues={ 'title': wikiTitle,
                                                     'page': obj,
                                                     'markers': markers,
                                                     'colors': colors })
        return    
class GWMap(GWRequestHandler):                                                  # Display the map page
    """ Display the map page """
    def get(self):
        """ Display the map page: gw/map?tag=... """
        #logging.info( '-----> GWMap' )
        tags = self.request.get( 'tag',                                         # check for tags
                                  allow_multiple=True, default_value=None )
        #logging.info( '       tags <<%s>>' % tags )
        features = GWQuery.query( tags )
        self.generate('map.html', 
                      templateValues={ 'title': 'Map', 'features': features })
        return    
class GWPageServer(webapp.RequestHandler):                                      # handle json page requests
    """ Handle Geo Wiki Server requests """
    def get( self ):                                                            # handle get request
        """ Return pages matching query 
            ex: /gw/pageserver?page=Great_Loop_Home
                /gw/pageserver?scale=5&box=34,30,-80,-84
            
            Options:
            p=Great_Loop_Home                                                   page, multiple
            t=tag1                                                              tags, multiple
            c=current                                                           'true' or 'false'
            b=north,south,east,west                                             bounding box
            s=scale                                                             scale
            o=flags                                                             output flags, multiple
                f                                                               output features with page
                fp                                                              output feature page features with page
        """
        logging.info( '-----> query' )
        pages = self.request.get( 'page',                                       # check for specific pages
                                  allow_multiple=True, default_value=[] )
        if( len(pages) == 0 ): pages = None
        logging.info( '       page %s' % pages )
        tags = self.request.get( 'tag', allow_multiple=True, default_value=[] )
        if( len(tags) == 0 ): tags = None
        logging.info( '       tags %s' % tags )
        current = self.request.get( 'current', default_value=None )
        if current: current = (current == 'true')
        logging.info( '       current %s' % current )
        output = self.request.get( 'output', allow_multiple=True, default_value=[] )
        logging.info( '       output %s' % output )
        results = GWPage.query( pages, tags, current )
        jlist = []
        for page in results:
            obj = page.json()                                                   # make a json.dumps compatible dictionary
            features = GWFeature.list( page.wikiTitle )                         # get the page's features
            if ('featurepages' in output) and (len(page.featurepages) > 0):
                for p in page.featurepages:
                    fp = GWFeature.list( p )
                    features.extend( fp )
            obj[ 'features' ] = [ f.json() for f in features]                   # put them in a json.dumps compatiable array of dictionaries
            jlist.append( obj )
            #logging.info( '       return %s' % page.wikiTitle )
        jsonMsg = json.dumps( jlist )
        logging.info( '       return %s items' % len(jlist) )
        logging.info( '       length %s' % len(jsonMsg) )
        self.response.headers['Content-Type'] = 'application/javascript'
        self.response.out.write(jsonMsg)
        return

#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# Geo wiki feature
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
class GWFeature( polymodel.PolyModel ):
    """ Wiki page features (points, lines, and shapes).
        NOTE: Features are associated with a wiki page revision.  Each revision 
        has its own set of features.  While this causes duplication it 
        significantly simplifies the application.
    """
    wikiTitle = db.StringProperty( required = True, multiline = False  )        # feature's parent page wikiTitle
    revision = db.IntegerProperty( required = True, default = 0 )               # the revision, as features are revised this is incremented
    ftype = db.StringProperty( required = True )                                # feature type (GW_TYPE_ICON, GW_TYPE_POLYGON, GW_TYPE_POLYLINE, GW_TYPE_KML)
    extra = db.StringProperty( )                                                # extra data for this feature (location, line length, shape area)
    
    def json( self ):                                                           # create json dumps compatible version of this feature
        """ Create json.dumps compatible version of this feature. Extended by children. """
        jsonObj = {
            'key': str( self.key() ),
            'wikiTitle': self.wikiTitle,
            'revision': self.revision,
            'ftype': self.ftype,
            'extra': self.extra
            }
        return jsonObj    
    @staticmethod
    def eraseAll():                                                             # erase all features
        """ Erase all features """
        q = GWFeature.all()
        for r in q: 
            db.delete( r )
        logging.info('-----> erased all features');
        return 'Erased all features.'
    @staticmethod
    def xmlCoordinatesParse( feature ):                                         # get and parse the coordinates
        """ Get, parse, and make points for the coordinates of a feature from an xml object """
        crd = feature.getElements( 'coordinates' )
        (crd, n) = re.subn("\n", ' ', crd[0].getData())                         # clean up \n
        crd = re.split(" +", crd)                                               # get each lat/lng/height
        points = []
        for c in crd:
            if c == '': continue
            lla = c.split(',')
            points.append( db.GeoPt(lla[1], lla[0]))
        return points
    @staticmethod
    def xmlInit( root, page ):                                                  # create a feature from XML embeded in a page
        """ create a feature object from an XML object """
        features = root.getElements( 'feature' )
        report = ''
        for f in features:
            ftype = f.attributes['type']
            if ftype == GW_TYPE_ICON: 
                report += GWPoint.xmlInit( f, page )
            elif ftype == GW_TYPE_POLYLINE: 
                report += GWLine.xmlInit( f, page )
            elif ftype == GW_TYPE_POLYGON: 
                report += GWShape.xmlInit( f, page )
        return report
    @staticmethod
    def list( wikiTitle ):                                                      # return list of page's features
        """ Return list of page's features. """
        features = []
        query = GWFeature.all()
        query = query.filter( 'wikiTitle =', wikiTitle )
        for f in query:
            features.append( f )
        return features

# fixme: verify that these are used    
    def jsonPoints( self ):                                                     # create json dumps compatible version of this features points fixme: is this used
        """ Create json.dumps compatible version of this feature's points """
        # deal with points
        lPoints = []
        for pt in self.points:
            lPoints.append( [pt.lat, pt.lon] )
        return lPoints
    
# below here may not be used, fixme    
    @staticmethod
    def jsonList( wikiTitle ):                                                  # Return json.dumps compatible list of all features for wikiTitle
        """ return list of all features for wikiTitle """
        fList = []
        features = GWFeature.all()
        features = features.filter( 'wikiTitle =', wikiTitle )
        for f in features:
            fList.append(f.json())
        return fList

class GWPoint( GWFeature ):                                                     # point feature
    """ Extend GWFeature for a point """
    icon = db.StringProperty( )                                                 # icon to display for this feature
    point = db.GeoPtProperty( default = None )                                  # icon location
    
    def json( self ):                                                           # Create json.dumps version of object
        """ Create json.dumps compatible version of this feature. """
        jsonObj = GWFeature.json( self )                                        # parent object
        jsonObj['icon'] = self.icon                                             # add our parts
        jsonObj['point'] = [self.point.lat, self.point.lon]
        return jsonObj
    @staticmethod
    def xmlInit( feature, page ):                                               # Create a new GWPoint from xml
        """ Create a new GWPoint from XML """
        icon = feature.attributes['icon']
        point = GWFeature.xmlCoordinatesParse( feature )[0]
        nf = GWPoint( wikiTitle = page.wikiTitle.lower(),
                      revision = page.revision,
                      ftype = GW_TYPE_ICON,
                      extra = '',                              # fixme: need to set
                      icon = icon,
                      point = point )
        nf.put()
        report = ( '&nbsp;&nbsp;&nbsp;Point: %s::%s::%s<br />' % (icon, point.lat, point.lon))
        return report
    def lats( self ):                                                           # return list of all latitudes
        """ Return list of all latitudes """
        return [ self.point.lat ]
    def lons( self ):                                                           # return list of all longitudes
        """ Return list of all longitudes """
        return [ self.point.lon ]

class GWLine( GWFeature ):                                                      # line feature
    """ Extend GWFeature for a line """
    color = db.StringProperty( )                                                # color to use for this feature
    width = db.IntegerProperty( )                                               # line width
    opacity = db.FloatProperty( )                                               # line opacity
    points = db.ListProperty( db.GeoPt, default = None )                        # points for this feature
    
    def json( self ):                                                           # Create json.dumps version of object
        """ Create json.dumps compatible version of this feature. """
        jsonObj = GWFeature.json( self )
        jsonObj['color'] = self.color
        jsonObj['width'] = self.width
        jsonObj['opacity'] = self.opacity
        jsonObj['points'] = self.jsonPoints()
        return jsonObj
    @staticmethod
    def xmlInit( feature, page ):                                               # Create a new GWLine from xml
        """ Create a new GWLine from XML """
        color = feature.attributes.get( 'color', GW_POLY_COLOR )
        width = feature.attributes.get( 'widht', GW_POLY_WIDTH )
        opacity = feature.attributes.get( 'opacity', GW_POLY_OPACITY )
        points = GWFeature.xmlCoordinatesParse( feature )
        nf = GWLine( wikiTitle = page.wikiTitle.lower(),
                     revision = page.revision,
                     ftype = GW_TYPE_POLYLINE,
                     extra = '',                                                #fixme: need to set to length of line
                     color = color,
                     width = int(width),
                     opacity = float(opacity),
                     points = points )
        nf.put()
        report = ( '&nbsp;&nbsp;&nbsp;Line: %s::%s::%s::%s::%s<br />' % 
                   (color, width, opacity, points[0].lat, points[0].lon))
        return report
    def lats( self ):                                                           # return list of all latitudes
        """ Return list of all latitudes """
        return [ p.lat for p in self.points ]
    def lons( self ):                                                           # return list of all longitudes
        """ Return list of all longitudes """
        return [ p.lon for p in self.points ]
    
class GWShape( GWFeature ):                                                     # shape feature
    """ Extend GWFeature for a shape """
    color = db.StringProperty( )                                                # color to use for this feature
    width = db.IntegerProperty( )                                               # line width
    opacity = db.FloatProperty( )                                               # line opacity
    fillcolor = db.StringProperty( )                                            # color to use for this feature
    fillopacity = db.FloatProperty( )                                           # line opacity
    points = db.ListProperty( db.GeoPt, default = None )                        # points for this feature
    
    def json( self ):                                                           # Create json.dumps version of object
        """ Create json.dumps compatible version of this feature. """
        jsonObj = GWFeature.json( self )
        jsonObj['color'] = self.color
        jsonObj['width'] = self.width
        jsonObj['opacity'] = self.opacity
        jsonObj['fillcolor'] = self.fillcolor
        jsonObj['fillopacity'] = self.fillopacity
        jsonObj['points'] = self.jsonPoints()
        return jsonObj
    @staticmethod
    def xmlInit( feature, page ):                                               # Create a new GWShape from xml
        """ Create a new GWShape from XML """
        color = feature.attributes.get( 'color', GW_POLY_COLOR )
        width = feature.attributes.get( 'widht', GW_POLY_WIDTH )
        opacity = feature.attributes.get( 'opacity', GW_POLY_OPACITY )
        fillcolor = feature.attributes.get( 'fillcolor', GW_POLY_COLOR )
        fillopacity = feature.attributes.get( 'fillopacity', GW_POLY_FILLOPACITY )
        points = GWFeature.xmlCoordinatesParse( feature )
        nf = GWShape( wikiTitle = page.wikiTitle.lower(),
                      revision = page.revision,
                      ftype = GW_TYPE_POLYGON,
                      extra = '',                        # fixme: set extra to area of polygon
                      color = color,
                      width = int(width),
                      opacity = float(opacity),
                      fillcolor = fillcolor,
                      fillopacity = float(fillopacity),
                      points = points )
        nf.put()
        report = ( '&nbsp;&nbsp;&nbsp;Shape: %s::%s::%s::%s::%s::%s::%s<br />' % 
                  (color, width, opacity, fillcolor, fillopacity, points[0].lat, points[0].lon))
        return report
    def lats( self ):                                                           # return list of all latitudes
        """ Return list of all latitudes """
        return [ p.lat for p in self.points ]
    def lons( self ):                                                           # return list of all longitudes
        """ Return list of all longitudes """
        return [ p.lon for p in self.points ]
    
class GWKML( GWFeature ):                                                       # kml feature, fixme: should have a kml import function
    """ Extend GWFeature for a KML file """
    kml = db.LinkProperty( )                                                    # URI for KML file for this feature for type GWM.KML

    def json( self ):                                                           # Create json.dumps version of object
        """ Create json.dumps compatible version of this feature. """
        jsonObj = GWFeature.json( self )
        jsonObj['kml'] = self.kml
        return jsonObj
    

#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# Geo wiki comment support
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
class GWComment(db.Model):                                                      # Geo wiki page comment # fixme: check if used and good
    """ Comments associated with a wiki page.  
        NOTE: The comment is associated with the page and any/all of its revisions
    """
    wikiTitle = db.StringProperty( required = True, multiline = False )         # the wiki page for this comment
                                                                                # use the title since the page object may change with updates
    revision = db.IntegerProperty( required = True )                            # the wiki page revision when this comment was entered
    comment = db.TextProperty(required=True)                                    # the page comment proper
    author = db.ReferenceProperty( GWUser, required = True )                    # most recent author of this comment
    date = db.DateTimeProperty(auto_now_add=True)                               # the creation date/time
    
    def json(self):
        """ Make an equilavent json style (json.dumps compatible ) object for 
            this comment 
        """
        jsonObj = {
            'wikiTitle': self.wikiTitle,
            'revision': self.revision,
            'comment': self.comment,
            'author': self.author.keyStr(),
            'nickname': self.author.nickname(),
            'date': self.date.strftime('%Y-%m-%d %H:%M')
            }
        return jsonObj
    @staticmethod
    def jsonlist( page ):
        """ return list of all comments for the page """
        comment = GWComment.all()
        comment = comment.filter( 'wikiTitle =', page.wikiTitle )
        cList = []
        for c in comment:
            cList.append(c.json())
        return cList
    @staticmethod
    def xmlInit( root, page ):
        """ create a feature object from XML """
        wikiTitle = root.attributes['wikiTitle'].lower()
        author = GWUser.get()
        report = ''
        comments = root.getElements( 'comment' )
        for c in comments:
            comment = c.cdata
            nc = GWComment(wikiTitle=wikiTitle, revision=1, 
                           comment=comment, author=author)
            nc.put()
            report += ( '&nbsp;&nbsp;&nbsp;comment: %s<br />' % comment )
        return report
    @staticmethod
    def jsonInit( comments, page ):
        """ make comments for this page  """
        return
    @staticmethod
    def eraseAll():
        q = GWComment.all()
        for r in q: 
            db.delete( r )
        logging.info('-----> erased all comments');
        return 'Erased all comments.'
    @staticmethod
    def load( overwrite ):
        report = '<br />Initialized comments.'                                        #todo: GWComment.load'
        return report

class GWQuery( db.Model ):                                                      # GW saved query support
    """ Holds JSON objects corresponding to various queries. """
    tags = db.StringProperty( default = [] )                                    # tags associated with this query, ex. 'a,b,c'
    json = db.TextProperty( required = True )                                   # holds json response object
    @staticmethod
    def query(                                                                  # return all pages matching this query
        tags=[] ):                                                              # tags
        """ Query pages """
        #logging.info( '-----> GWQuery query' )
        # fixme: verify that tag exists only once
        tags.sort()
        tagstr = 'tags:' + (','.join(tags))
        #logging.info( '       tag string <<%s>>' % tagstr )
        qQuery = GWQuery.all()                                                  # build the query
        qQuery = qQuery.filter( 'tags =', tagstr )
        result = qQuery.get()
        if result:                                                              # query saved
            #logging.info( '       found' )
            return result.json
        # need to build this query response
        qpages = GWPage.all()                                                   # build the page query
        qpages = qpages.filter( 'current =', True )
        if len(tags) > 0:
            qpages = qpages.filter( 'tags IN', tags )
        results = []
        for p in qpages:
            #logging.info( '       page %s' % p.wikiTitle )
            qfeatures = GWFeature.all()                                         # get the page's features
            qfeatures = qfeatures.filter( 'wikiTitle =', p.wikiTitle )
            qfeatures = qfeatures.filter( 'revision =', p.revision )
            for f in qfeatures:
                # fixme: verify that feature exists only once in the list
                results.append( f.json() )
        #logging.info( '       return %s items' % len(results) )
        jsonMsg = json.dumps( results )
        query = GWQuery( tags=tagstr, json=jsonMsg )
        query.put()
        return jsonMsg
    # fixme: WARNING  2009-06-01 17:05:46,967 py_zipimport.py:103] Can't open zipfile C:\Python25\lib\site-packages\setuptools-0.6c7-py2.5.egg: IOError: [Errno 13] file not accessible: 'C:\\Python25\\lib\\site-packages\\setuptools-0.6c7-py2.5.egg'
    # fixme: WARNING  2009-06-01 17:05:46,983 py_zipimport.py:103] Can't open zipfile C:\Python25\lib\site-packages\simplejson-2.0.9-py2.5-win32.egg: IOError: [Errno 13] file not accessible: 'C:\\Python25\\lib\\site-packages\\simplejson-2.0.9-py2.5-win32.egg'
    @staticmethod
    def eraseAll():                                                             # erase all pages
        q = GWQuery.all()
        for r in q: 
            db.delete( r )
        return
    
class GWPageList(GWRequestHandler):                                             # Display HTML list of GW pages # fixme: check if used and good
    """ Display HTML page to list GeoWiki pages. """
    def get(self):
        """ Display HTML page to list GeoWiki pages. """
        self.generate('list.html', templateValues={'title': 'GeoWiki Page List'}) 
        return

class GWPagesJson(GWRequestHandler):                                            # Get Json list of HTML pages # fixme: check if used and good
    """ Return json object with list of pages consistent with current filter. """
    def get(self):
        """ Return json object with list of pages consistent with current filter. """
        pagelist = []
        pages = GWPage.all()
        pages = pages.filter( 'current =', True )
        pages = pages.order( 'title' )
        pages = pages.order( '-title' )
        # todo: filter code goes in here
        for page in pages:
            pagelist.append( page.json())
        # turn into a JSON object
        jsonObj = {'pages': pagelist }
        jsonmsg = json.dumps(jsonObj)
        #logging.debug( jsonmsg )
        # return it
        self.response.headers['Content-Type'] = 'application/javascript'
        self.response.out.write(jsonmsg)
    
class GWPageJson(webapp.RequestHandler):                                        # Save a GeoWiki page # fixme: check if used and good
    """ Handles all requests to /gw/json/page
    Wiki handles get, post, and delete requests of the form:
        /gw/json/page/{wikiName}
        /gw/json/page/{wikiName}/{version}
    """
    def post( self, wikiTitle ):
        """ Replace the page """
        if not GWUser.isLogin():
            self.response.headers['Content-Type'] = 'text/javascript'
            self.response.set_status( 401, 'Must logon' )
            return
        data = self.request.body
        page = json.loads( data )                                               # NOTE: Page may contain features
        page['wikiTitle'] = wikiTitle
        status, message = GWPage.save( page );
        self.response.headers['Content-Type'] = 'text/javascript'
        self.response.set_status( status, message )
        return

class GWPageWiki(webapp.RequestHandler):                                        # fixme: is this used? # fixme: check if used and good
    """ Handles all requests to /wiki
    Wiki handles get, post, and delete requests of the form:
        /wiki/{wikiName}
        /wiki/{wikiName}/{version}

    todo: need to implement this after moving functionality into gwpage.py  should be part of GWPageJson
    """
        
    def get( self, pageTitle):
        status, wikiPage = GWPage.getRevision( pageTitle )
        if status != 200:
            #self.response.headers['Content-Type'] = 'text/HTML'
            #self.response.set_status( status )
            self.error( status )
            self.response.out.write("%s %s" % (status, wikiPage))
            return


